本章会在正式控制流章节前少量使用 for/if/enumerate。先掌握最小定义:for 逐项取值,if 只在条件为真时执行,enumerate 同时给出序号与元素;复杂分支和循环设计仍在第8—9章系统学习。
即时练习:先预测输出,再把条件改为 code=='B';若不能逐轮写出 position/code,先完成本页再进入列表实战。
推导式 [表达式 for 元素 in 序列 if 条件] 是“循环取值+条件保留”的紧凑写法;本章只要求能把它还原成普通循环,第9章再讨论复杂写法。
即时反馈:输出应为 [2,4];若推导式顺序不清楚,作答时可以使用普通循环,不要求在第2章独立编写复杂推导式。
在金融数据分析中,我们经常需要处理成组的相关数据。
Python提供了三种核心复合数据类型:
| 类型 | 特点 | 典型应用 |
|---|---|---|
| 列表 (List) | 可变、有序 | 股价时间序列 |
| 字典 (Dictionary) | 键值对、快速查找 | 股票信息存储 |
| 元组 (Tuple) | 外层槽位固定;成员另行管理 | 固定记录结构 |
从计算机科学角度看,三种数据类型代表了不同的抽象层次:
列表是Python中最常用的数据结构之一:
在金融应用中,列表常用于存储:
x 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。x 的结果;同时写出方向、数量级或表格/图形结构。运行后核对:核对 x 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。
拓展练习:只改变一个关键输入或业务场景,先预测输出如何变化,再运行验证并解释变化原因。
列表可以包含不同类型的元素(这是Python列表的独特优势):
'finance'、数值 8.88 可以共存关键时间复杂度:
| 操作 | 复杂度 | 说明 |
|---|---|---|
x[i] 索引访问 |
O(1) | 直接定位 |
x[a:b] 切片 |
O(k) | k为切片长度 |
len(x) |
O(1) | 内置存储长度 |
x.append() |
均摊O(1) | 动态数组扩容 |
# 创建初始投资组合(存储A股股票代码)
portfolio = ['600519.SH', '000858.SZ', '600036.SH', '000002.SZ']
# 6位数字.SH(上海)或.SZ(深圳)
# 添加新股票(末尾追加,均摊O(1))
portfolio.append('601318.SH') # 中国平安
# 移除股票(需遍历查找,O(n))
portfolio.remove('000002.SZ') # 万科A
# 输出当前持仓
print(f'当前持仓股票数量: {len(portfolio)}')
for i, stock in enumerate(portfolio, start=1):
print(f' {i}. {stock}')当前持仓股票数量: 4
1. 600519.SH
2. 000858.SZ
3. 600036.SH
4. 601318.SH
| 方法 | 功能 | 示例 |
|---|---|---|
append(x) |
末尾添加元素 | lst.append('new') |
remove(x) |
删除第一个匹配 | lst.remove('old') |
insert(i,x) |
指定位置插入 | lst.insert(0,'first') |
pop(i) |
删除并返回元素 | lst.pop(-1) |
| 方法 | 功能 | 示例 |
|---|---|---|
sort() |
原地排序 | lst.sort() |
reverse() |
原地反转 | lst.reverse() |
index(x) |
查找元素位置 | lst.index('target') |
注意:append、sort、reverse 都是原地修改,返回 None。
字典是Python中最重要的数据结构之一:
字典的核心特点是键值对 (Key-Value Pair) 结构:
dict1 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。dict1 的结果;同时写出方向、数量级或表格/图形结构。运行后核对:核对 dict1 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。
拓展练习:只改变一个关键输入或业务场景,先预测输出如何变化,再运行验证并解释变化原因。
字典底层使用哈希表 (Hash Table) 实现:
hash(key) 计算哈希值| 操作 | 字典 | 列表 |
|---|---|---|
| 查找元素 | 平均 O(1) | O(n) |
| 插入元素 | 平均/摊销 O(1) | 末尾追加摊销 O(1),中间插入 O(n) |
| 删除元素 | 平均 O(1) | 末尾弹出 O(1),按位置移动 O(n) |
| 遍历全部 | O(n) | O(n) |
键的要求:必须可哈希。int、str 通常可哈希;元组仅在所有元素均可哈希时可作键,list、dict 不可作键。
# 创建股票信息字典
stock_info = {
'code': '600519.SH',
'name': '贵州茅台',
'price': 1850.00,
'volume': 2500000
}
# 更新股价
stock_info['price'] = 1860.00
# 添加新字段(市盈率)
stock_info['pe_ratio'] = 45.5
# 检查字段是否存在
if 'market_cap' in stock_info:
print(f'市值: {stock_info["market_cap"]}')
else:
print('市值信息缺失')
# 批量更新
stock_info.update({'price': 1870.00, 'volume': 2600000})
print(stock_info)市值信息缺失
{'code': '600519.SH', 'name': '贵州茅台', 'price': 1870.0, 'volume': 2600000, 'pe_ratio': 45.5}
| 方法 | 功能 | 示例 |
|---|---|---|
d[key] |
获取值(不存在报错) | d['name'] |
d.get(key, default) |
安全获取值 | d.get('name', '未知') |
d[key] = val |
设置/更新值 | d['price'] = 100 |
d.update(other) |
批量更新 | d.update({...}) |
| 方法 | 功能 | 示例 |
|---|---|---|
key in d |
检查键是否存在 | 'name' in d |
d.keys() |
获取所有键 | 返回视图对象 |
d.values() |
获取所有值 | 返回视图对象 |
d.items() |
获取所有键值对 | 返回视图对象 |
keys()、values()、items() 返回的是视图对象:
共有键: {'c', 'b'}
A独有键: {'a'}
视图对象支持集合运算:&(交集)、|(并集)、-(差集)
元组是槽位不可变的有序序列;若槽位引用列表等可变对象,对象内部内容仍可变化。
不可变性带来的优势:
tup 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。tup 的结果;同时写出方向、数量级或表格/图形结构。运行后核对:核对 tup 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。
拓展练习:只改变一个关键输入或业务场景,先预测输出如何变化,再运行验证并解释变化原因。
切片规则 tup[start:end]:
start 索引,不包含 end 索引tup[1:3] → 索引1和索引2的元素标准元组只能通过索引访问,代码可读性差。命名元组解决了这个问题:
股票名称: 贵州茅台
股票价格: 1850.0
股票代码: 600519.SH
根据使用场景选择合适的数据类型:
| 场景 | 推荐类型 | 理由 |
|---|---|---|
| 时间序列数据 | 列表 | 有序、可动态增删 |
| 键值对数据 | 字典 | O(1)快速查找 |
| 固定记录结构 | 元组 | 外层槽位固定;校验与权限另管 |
| 去重数据 | 集合 | 自动去重 |
| 矩阵运算 | NumPy数组 | 高性能数值计算 |
# 导入重复计时与稳健汇总工具以避免单次观测误导。
import statistics,timeit
element_count=1_000_000 # 固定两种结构包含相同数量的键。
test_list=list(range(element_count)) # 建立需要线性扫描的列表。
test_dict={key:None for key in range(element_count)} # 建立按哈希定位的字典。
target_key=element_count-1 # 选择列表末端键以展示线性扫描负载。
repeat_count=7 # 用七轮重复观察运行环境波动。
lookups_per_repeat=10 # 每轮对两种结构执行相同次数的查询。
timer_context={'target_key':target_key,'test_list':test_list,'test_dict':test_dict} # 共享同一计时上下文。
list_samples=timeit.repeat('target_key in test_list',globals=timer_context,repeat=repeat_count,number=lookups_per_repeat) # 重复测量列表查询。
dict_samples=timeit.repeat('target_key in test_dict',globals=timer_context,repeat=repeat_count,number=lookups_per_repeat) # 重复测量字典查询。
list_per_lookup=[sample/lookups_per_repeat for sample in list_samples] # 换算列表单次查询耗时。
dict_per_lookup=[sample/lookups_per_repeat for sample in dict_samples] # 换算字典单次查询耗时。
ratio_samples=[list_value/dict_value for list_value,dict_value in zip(list_per_lookup,dict_per_lookup)] # 逐轮计算同环境耗时比。
print(f'列表单次中位数={statistics.median(list_per_lookup):.6f}秒,范围=[{min(list_per_lookup):.6f}, {max(list_per_lookup):.6f}]') # 报告列表稳健汇总。
print(f'字典单次中位数={statistics.median(dict_per_lookup):.9f}秒,范围=[{min(dict_per_lookup):.9f}, {max(dict_per_lookup):.9f}]') # 报告字典稳健汇总。
print(f'同轮耗时比中位数={statistics.median(ratio_samples):.1f}倍,范围=[{min(ratio_samples):.1f}, {max(ratio_samples):.1f}]') # 报告波动而非固定倍数。列表单次中位数=0.003882秒,范围=[0.003872, 0.003888]
字典单次中位数=0.000000046秒,范围=[0.000000045, 0.000000249]
同轮耗时比中位数=84441.5倍,范围=[15602.4, 86440.4]
解释边界:列表成员查询为 \(O(n)\);字典成员查询平均为 \(O(1)\),但建表成本、内存、哈希冲突与实际查询组合也要计入决策。
import copy
# 嵌套列表(模拟投资组合持仓)
portfolio = [
['600519.SH', 100, 1850.00],
['000858.SZ', 200, 85.50]
]
# 浅拷贝:内层仍然共享引用
portfolio_shallow = portfolio.copy()
# 深拷贝:完全独立的副本
portfolio_deep = copy.deepcopy(portfolio)
# 修改原始数据
portfolio[0][2] = 1900.00
# 浅拷贝受影响(共享内层引用)
print(f'浅拷贝: {portfolio_shallow[0][2]}') # 1900.00
# 深拷贝不受影响(完全独立)
print(f'深拷贝: {portfolio_deep[0][2]}') # 1850.00浅拷贝: 1900.0
深拷贝: 1850.0
| 场景 | 方法 | 说明 |
|---|---|---|
| 简单列表(不含嵌套) | list.copy() 或 [:] |
浅拷贝即可 |
| 嵌套结构且须隔离可变成员 | copy.deepcopy() |
先确认成员可复制、成本可接受 |
| 简单字典 | dict.copy() |
浅拷贝 |
| 嵌套字典且须隔离可变成员 | copy.deepcopy() |
若约定只读或共享可用浅拷贝 |
记住:先明确谁拥有并可修改内层对象;只有需要递归隔离且已评估性能、资源句柄与自定义对象语义时才使用深拷贝。
三大数据类型核心特征:
[] 创建
{} 创建
() 创建
copy.deepcopy()holding_list=[('002415.SZ',.40),('600276.SH',.35),('600104.SH',.25)] # 建立有序持仓列表
holding_list.append(('002230.SZ',.10)) # 用追加记录新增证券。
holding_list[1]=('600276.SH',.25) # 用索引更新已有证券权重。
holding_dict=dict(holding_list) # 建立证券代码到权重的查询映射。
holding_dict['600104.SH']=.20 # 用键更新已有证券权重。
query_code='600276.SH' # 指定需要核对的证券代码。
query_weight=holding_dict[query_code] # 按键读取该证券权重。
configuration=('季度再平衡','CNY','长三角') # 建立外层槽位固定的配置元组。
configuration_slice=configuration[:2] # 切片提取频率与币种且不改原元组。
structure_choices={'需要修改':'列表或字典','需要按键查找':'字典','外层槽位固定':'元组'} # 回答结构选择三问。
assert query_weight==.25 and configuration==('季度再平衡','CNY','长三角') # 核对查询值与原元组。
print({'list':holding_list,'lookup':query_weight,'tuple_slice':configuration_slice}) # 输出列表、查询与切片核心结果。
print({'three_questions':structure_choices}) # 输出三问选择及其理由线索。industry_holding_list=[('002415.SZ',.60)] # 建立电子行业有序持仓清单。
industry_holding_list.append(('002230.SZ',.40)) # 追加同一行业证券。
industry_weight_by_code=dict(industry_holding_list) # 转成按代码查询的行业映射。
industry_query=industry_weight_by_code['002230.SZ'] # 查询新增证券权重。
assert len(industry_holding_list)==2 and industry_query==.40 # 核对追加与按键查询结果。
print({'industry_positions':industry_holding_list,'lookup':industry_query}) # 输出可新案例的行业持仓依据。[商业大数据分析与应用]